/* Copyright (c) 2004-2008, Andriy Nikolov
 * All rights reserved.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
package org.processmining.email;

import java.util.*;
import java.io.*;
import org.xml.sax.*;
import org.xml.sax.helpers.*;
//import org.apache.xerces.parsers.SAXParser;
import java.text.*;
import javax.xml.parsers.*;

/**
 * <p>Represents an e-mail log imported by the application
 *
 * @author Andriy Nikolov
 * @version 1.0
 */
public class EMailLog {

  Hashtable messageList;
  LinkedList orderedMessages;
  LinkedList employeesList;
  Vector employeesActiveFlags;

  Sociogram sociogram;
  ProcessLog processLog;

  boolean sorted = false;

  public EMailLog() {
    messageList = new Hashtable();
    employeesList = new LinkedList();
    employeesActiveFlags = new Vector();
    orderedMessages = new LinkedList();
  }

  /**
   * Adds a message into the log
   *
   * @param message EMailMessage
   */
  public void addMessage(EMailMessage message) {
    Enumeration enumMessages;
    EMailMessage iterMessage;
/*    if(!message.allFieldsFilled()) {
      return;
    }*/
    if(messageList.containsKey(message.id)) {
      return;
    }
    enumMessages = messageList.elements();
    while(enumMessages.hasMoreElements()) {
      iterMessage = (EMailMessage)enumMessages.nextElement();
      if(iterMessage.isEqualTo(message)) {
        return;
      }
    }
    messageList.put(message.id, message);
    orderedMessages.add(message);
    sorted = false;
  }

  /**
   * returns a Hashtable of messages. The keys in the table are message ids.
   *
   * @return Hashtable
   */
  public Hashtable getMessageList() {
    return messageList;
  }

  private boolean messageExists (EMailMessage message) {
    if(messageList.containsKey(message.id)) {
      return true;
    }

    return false;
  }

  private void extractAllEmployees() {
    Enumeration enumMessages;
    EMailMessage iterMessage;
    enumMessages = messageList.elements();
    while(enumMessages.hasMoreElements()) {
      iterMessage = (EMailMessage)enumMessages.nextElement();
      if(!employeesList.contains(iterMessage.getFrom())) {
        employeesList.add(iterMessage.getFrom());
      }
      for(int i=0;i<iterMessage.getTo().size();i++) {
        if(!employeesList.contains(iterMessage.getTo().get(i))) {
          employeesList.add(iterMessage.getTo().get(i));
        }
      }
      for(int i=0;i<iterMessage.getCc().size();i++) {
        if(!employeesList.contains(iterMessage.getCc().get(i))) {
          employeesList.add(iterMessage.getCc().get(i));
        }
      }
      for(int i=0;i<iterMessage.getBcc().size();i++) {
        if(!employeesList.contains(iterMessage.getBcc().get(i))) {
          employeesList.add(iterMessage.getBcc().get(i));
        }
      }
    }
    for (int i=0;i<employeesList.size();i++) {
      employeesActiveFlags.add(new Boolean(true));
    }
  }

  /**
   * Loads the list of e-mail messages from an XML file. If the e-mail log
   * already contained messages then new messages will be appended
   *
   * @param fileName String
   * @return int
   */
  public int loadFromFile(String fileName) {
    int oldTotal;
    oldTotal = messageList.size();
    try {
        SAXParserFactory parserFactory = SAXParserFactory.newInstance();
        parserFactory.setValidating(false);
        parserFactory.setNamespaceAware(true);
        EMailLogSaxParser eMailLogSaxParserInstance = new EMailLogSaxParser();
        eMailLogSaxParserInstance.setEMailLog(this);
        SAXParser parser = parserFactory.newSAXParser();
        parser.parse(fileName, eMailLogSaxParserInstance);
    } catch (IOException exception) {
        exception.printStackTrace();
        return -1;
    } catch (SAXException exception) {
        exception.printStackTrace();
    } catch (ParserConfigurationException exception) {
        exception.printStackTrace();
    } catch (FactoryConfigurationError exception) {
        exception.printStackTrace();
    }
    return messageList.size()-oldTotal;
  }

  /**
   * Produces a sociogram based on the log's list of messages and calculates its
   * weights.
   *
   * @param useThreadsOnly boolean - The flag specifies if the sociogram will
   *   use all messages or only message threads. In the later case all reply
   *   messages (with subject starting with Re:) will be excluded
   *   	false - use all messages;
   *   	true - only threads.
   * @return Sociogram
   */
  public Sociogram getNewSociogram(boolean useThreadsOnly) {
//    extractAllEmployees();
    EMailRecipient tmp;
    LinkedList activeEmployeesList = new LinkedList();
    for(int i=0;i<employeesList.size();i++) {
      tmp = (EMailRecipient)employeesList.get(i);
      if(tmp.isActive()) {
        activeEmployeesList.add(tmp);
      }
    }
    sociogram = new Sociogram(activeEmployeesList);
    sociogram.setUseThreadsOnly(useThreadsOnly);
    sociogram.build(messageList.elements());
    return sociogram;
  }

  /**
   * Produces a sociogram based on the log's list of messages. The same as
   * getNewSociogram(false)
   *
   * @return Sociogram
   */
  public Sociogram getSociogram() {
    if(sociogram!=null) {
      return sociogram;
    } else {
      return getNewSociogram(false);
    }
  }

  public ProcessLog getProcessLog() {
    if(processLog==null) {
      processLog = new ProcessLog(this);
      processLog.build();
    }
    return processLog;
  }

  public void setProcessLog(ProcessLog log) {
    processLog = log;
  }

  /**
   * Writes all content of the object into the PrintWriter
   *
   * @param writer PrintWriter
   */
  public void save(PrintWriter writer) {
    Enumeration enumMessages;
    EMailMessage iterMessage;

    writer.println("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>");
    writer.print("<EMailLog xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" ");
    writer.println("xsi:noNamespaceScemaLocation=\"EMailLog.xsd\">");
    enumMessages = messageList.elements();
    while(enumMessages.hasMoreElements()) {
      iterMessage = (EMailMessage)enumMessages.nextElement();
      if(iterMessage.isActive()) {
        iterMessage.save(writer);
      }
    }
    writer.println("</EMailLog>");
  }

  /**
   * Gives a message list in a LinkedList object
   *
   * @return LinkedList
   */
  public LinkedList getOrderedMessages() {
    if (!sorted) {
      Collections.sort(orderedMessages, new TimestampOwnerComparator());
      sorted = true;
    }
    return orderedMessages;
  }

  /**
   * Creates an instance of EMailRecipient object by the name and address of the
   * recipient. If the name and/or address already exist in the EMailLog object
   * then returns the EMailRecipient created before and adds new aliases to it.
   *
   * @param name Recipient's name
   * @param address Recipient's e-mail address
   * @return EMailRecipient
   */
  public EMailRecipient createEMailRecipient(String name, String address) {
    EMailRecipient answer, current;
    boolean alreadyExists = false;
    answer = new EMailRecipient(name);
    if (address!="")
      answer.addAlias(address);
    for (int i=0;i<employeesList.size();i++) {
      current = (EMailRecipient) employeesList.get(i);
      if (answer.isProbablyEqual(current)) {
        current.merge(answer);
        answer = current;
        alreadyExists = true;
        break;
      }
    }
    if (!alreadyExists) {
      employeesList.add(answer);
    }
    return answer;
  }

  /**
   * Returns the list of all e-mail recipients
   *
   * @return LinkedList
   */
  public LinkedList getEmployeesList() {
    return employeesList;
  }

  /**
   * Merges the records of two e-mail recipients
   *
   * @param rec1 EMailRecipient
   * @param rec2 EMailRecipient
   */
  public void mergeRecipients(EMailRecipient rec1, EMailRecipient rec2) {
    rec1.merge(rec2);
    employeesList.remove(rec2);
  }
}
